/*
 * This file is part of aion-emu <aion-emu.com>.
 *
 * aion-emu is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aion-emu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with aion-emu.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionemu.commons.services;

import java.io.File;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javolution.util.FastMap;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import com.aionemu.commons.scripting.scriptmanager.ScriptManager;

/**
 * Script Service class that is designed to manage all loaded contexts
 * 
 * @author SoulKeeper
 */
public class ScriptService
{
	/**
	 * Logger
	 */
	private static final Logger				log	= Logger.getLogger(ScriptService.class);

	/**
	 * Container for ScriptManagers, sorted by file
	 */
	private final Map<File, ScriptManager>	map	= new FastMap<File, ScriptManager>().shared();

	/**
	 * Loads script descriptor from given directory or file
	 * 
	 * @param file
	 *            directory or file
	 * @throws RuntimeException
	 *             if failed to load script descriptor
	 */
	public void load(String file) throws RuntimeException
	{
		load(new File(file));
	}

	/**
	 * Loads script descriptor from given file or directory
	 * 
	 * @param file
	 *            file that has to be loaded
	 * @throws RuntimeException
	 *             if something went wrong
	 */
	public void load(File file) throws RuntimeException
	{
		if(file.isFile())
		{
			loadFile(file);
		}
		else if(file.isDirectory())
		{
			loadDir(file);
		}
	}

	/**
	 * Load script descriptor from given file
	 * 
	 * @param file
	 *            script descriptor
	 */
	private void loadFile(File file)
	{
		if(map.containsKey(file))
		{
			throw new IllegalArgumentException("ScriptManager by file:" + file + " already loaded");
		}

		ScriptManager sm = new ScriptManager();
		try
		{
			sm.load(file);
		}
		catch(Exception e)
		{
			log.error(e);
			throw new RuntimeException(e);
		}

		map.put(file, sm);
	}

	/**
	 * Loads all files from given directory
	 * 
	 * @param dir
	 *            directory to scan for files
	 */
	private void loadDir(File dir)
	{
		for(Object file : FileUtils.listFiles(dir, new String[] { "xml" }, false))
		{
			loadFile((File) file);
		}
	}

	/**
	 * Unloads given script descriptor
	 * 
	 * @param file
	 *            script descriptor
	 * @throws IllegalArgumentException
	 *             if descriptor is not loaded
	 */
	public void unload(File file) throws IllegalArgumentException
	{
		ScriptManager sm = map.remove(file);
		if(sm == null)
		{
			throw new IllegalArgumentException("ScriptManager by file " + file + " is not loaded.");
		}

		sm.shutdown();
	}

	/**
	 * Reloads given script descriptor
	 * 
	 * @param file
	 *            Script Descriptro
	 * @throws IllegalArgumentException
	 *             if descriptor is not loaded
	 */
	public void reload(File file) throws IllegalArgumentException
	{
		ScriptManager sm = map.get(file);
		if(sm == null)
		{
			throw new IllegalArgumentException("ScriptManager by file " + file + " is not loaded.");
		}

		sm.reload();
	}

	/**
	 * Adds script manager to script service.<br>
	 * Should be used if scriptManager uses custom loading logic for scripts.
	 * 
	 * @param scriptManager
	 *            Script manager object
	 * @param file
	 *            script descriptor file
	 */
	public void addScriptManager(ScriptManager scriptManager, File file)
	{
		if(map.containsKey(file))
		{
			throw new IllegalArgumentException("ScriptManager by file " + file + " is already loaded.");
		}

		map.put(file, scriptManager);
	}

	/**
	 * Returns unmodifiable map with loaded script managers
	 * 
	 * @return unmodifiable map
	 */
	public Map<File, ScriptManager> getLoadedScriptManagers()
	{
		return Collections.unmodifiableMap(map);
	}

	/**
	 * Broadcast shutdown to all attached Script Managers.
	 */
	public void shutdown()
	{
		for(Iterator<Entry<File, ScriptManager>> it = this.map.entrySet().iterator(); it.hasNext();)
		{
			try
			{
				it.next().getValue().shutdown();
			}
			catch(Exception e)
			{
				log.warn("An exception occured during shudown procedure.", e);
			}

			it.remove();
		}
	}
}
